/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <melon/mac_address.h>

#include <cassert>
#include <ostream>

#include <melon/exception.h>
#include <melon/format.h>
#include <melon/ip_address_v6.h>
#include <melon/string.h>

using std::invalid_argument;
using std::string;

namespace melon {
    const MacAddress MacAddress::BROADCAST{Endian::big(uint64_t(0xffffffffffffU))};
    const MacAddress MacAddress::ZERO;

    MacAddress::MacAddress(StringPiece str) {
        memset(&bytes_, 0, 8);
        parse(str);
    }

    MacAddress MacAddress::createMulticast(IPAddressV6 v6addr) {
        // This method should only be used for multicast addresses.
        assert(v6addr.isMulticast());

        uint8_t bytes[SIZE];
        bytes[0] = 0x33;
        bytes[1] = 0x33;
        memcpy(bytes + 2, v6addr.bytes() + 12, 4);
        return fromBinary(ByteRange(bytes, SIZE));
    }

    string MacAddress::toString() const {
        static const char hexValues[] = "0123456789abcdef";
        string result;
        result.resize(17);
        result[0] = hexValues[getByte(0) >> 4];
        result[1] = hexValues[getByte(0) & 0xf];
        result[2] = ':';
        result[3] = hexValues[getByte(1) >> 4];
        result[4] = hexValues[getByte(1) & 0xf];
        result[5] = ':';
        result[6] = hexValues[getByte(2) >> 4];
        result[7] = hexValues[getByte(2) & 0xf];
        result[8] = ':';
        result[9] = hexValues[getByte(3) >> 4];
        result[10] = hexValues[getByte(3) & 0xf];
        result[11] = ':';
        result[12] = hexValues[getByte(4) >> 4];
        result[13] = hexValues[getByte(4) & 0xf];
        result[14] = ':';
        result[15] = hexValues[getByte(5) >> 4];
        result[16] = hexValues[getByte(5) & 0xf];
        return result;
    }

    Expected<Unit, MacAddressFormatError> MacAddress::trySetFromString(
        StringPiece value) {
        return setFromString(value, [](auto _, auto) { return makeUnexpected(_); });
    }

    void MacAddress::setFromString(StringPiece value) {
        setFromString(value, [](auto, auto _) { return _(), unit; });
    }

    Expected<Unit, MacAddressFormatError> MacAddress::trySetFromBinary(
        ByteRange value) {
        return setFromBinary(value, [](auto _, auto) { return makeUnexpected(_); });
    }

    void MacAddress::setFromBinary(ByteRange value) {
        setFromBinary(value, [](auto, auto _) { return _(), unit; });
    }

    template<typename OnError>
    Expected<Unit, MacAddressFormatError> MacAddress::setFromString(
        StringPiece str, OnError err) {
        // Helper function to convert a single hex char into an integer
        auto isSeparatorChar = [](char c) { return c == ':' || c == '-'; };

        uint8_t parsed[SIZE];
        auto p = str.begin();
        for (unsigned int byteIndex = 0; byteIndex < SIZE; ++byteIndex) {
            if (p == str.end()) {
                return err(MacAddressFormatError::Invalid, [&] {
                    throw invalid_argument(
                        sformat("invalid MAC address '{}': not enough digits", str));
                });
            }

            // Skip over ':' or '-' separators between bytes
            if (byteIndex != 0 && isSeparatorChar(*p)) {
                ++p;
                if (p == str.end()) {
                    return err(MacAddressFormatError::Invalid, [&] {
                        throw invalid_argument(
                            sformat("invalid MAC address '{}': not enough digits", str));
                    });
                }
            }

            // Parse the upper nibble
            uint8_t upper = detail::hexTable[static_cast<uint8_t>(*p)];
            if (upper & 0x10) {
                return err(MacAddressFormatError::Invalid, [&] {
                    throw invalid_argument(
                        sformat("invalid MAC address '{}': contains non-hex digit", str));
                });
            }
            ++p;

            // Parse the lower nibble
            uint8_t lower;
            if (p == str.end()) {
                lower = upper;
                upper = 0;
            } else {
                lower = detail::hexTable[static_cast<uint8_t>(*p)];
                if (lower & 0x10) {
                    // Also accept ':', '-', or '\0', to handle the case where one
                    // of the bytes was represented by just a single digit.
                    if (isSeparatorChar(*p)) {
                        lower = upper;
                        upper = 0;
                    } else {
                        return err(MacAddressFormatError::Invalid, [&] {
                            throw invalid_argument(sformat(
                                "invalid MAC address '{}': contains non-hex digit", str));
                        });
                    }
                }
                ++p;
            }

            // Update parsed with the newly parsed byte
            parsed[byteIndex] = (upper << 4) | lower;
        }

        if (p != str.end()) {
            // String is too long to be a MAC address
            return err(MacAddressFormatError::Invalid, [&] {
                throw invalid_argument(
                    sformat("invalid MAC address '{}': found trailing characters", str));
            });
        }

        // Only update now that we have successfully parsed the entire
        // string.  This way we remain unchanged on error.
        return setFromBinary(ByteRange(parsed, SIZE), err);
    }

    template<typename OnError>
    Expected<Unit, MacAddressFormatError> MacAddress::setFromBinary(
        ByteRange value, OnError err) {
        if (value.size() != SIZE) {
            return err(MacAddressFormatError::Invalid, [&] {
                throw invalid_argument(
                    sformat("MAC address must be 6 bytes long, got ", value.size()));
            });
        }
        memcpy(bytes_ + 2, value.begin(), SIZE);
        return unit;
    }

    std::ostream &operator<<(std::ostream &os, MacAddress address) {
        os << address.toString();
        return os;
    }
} // namespace melon
